package org.swift.mashup.wsdl;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.wsdl.Binding;
import javax.wsdl.Definition;
import javax.wsdl.Port;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.soap.SOAPAddress;
import javax.wsdl.extensions.soap12.SOAP12Binding;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;
import javax.xml.namespace.QName;
import javax.xml.soap.Detail;
import javax.xml.soap.DetailEntry;
import javax.xml.soap.SOAPFault;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.soap.SOAPBinding;
import javax.xml.ws.soap.SOAPFaultException;

import org.apache.axiom.om.OMElement;
import org.apache.axis2.AxisFault;
import org.apache.commons.lang.StringUtils;
import org.swift.commons.client.Call;
import org.swift.commons.xml.OMUtils;

import com.ibm.wsdl.extensions.soap.SOAPBindingImpl;

@SuppressWarnings("unchecked")
public final class OperationCall implements Call {
	
	private final static Logger log = Logger.getLogger("org.swift.mashup.wsdl.OperationCall");
	
	private final Service service;
	
	private final static Class PREFERRED_SOAP_VERSION = SOAP12Binding.class;
	
	private final Dispatch dispatch;
	
	private final String operationName;
	
	private AxiomMessage axMessage;
	
	private String url;
	
	public OperationCall(final String wsdlUrl, final String operationName, final String sampleRequest) throws WsdlLoadException {
		this.operationName = operationName;
		try {
			if (StringUtils.isNotEmpty(sampleRequest)){
				axMessage = new AxiomMessage(sampleRequest);
			}
			//this.sampleRequest = OMUtils.stringToOMElement(sampleRequest);
			WSDLFactory wsdlFactory = WSDLFactory.newInstance();
			WSDLReader reader = wsdlFactory.newWSDLReader();
			Definition definition = reader.readWSDL(wsdlUrl);
			
			Map services = definition.getServices();
			Entry<QName, javax.wsdl.Service> serviceEntry = (Entry<QName, javax.wsdl.Service>) services.entrySet().iterator().next();
						
			this.service = Service.create(new URL(wsdlUrl), serviceEntry.getKey());
			this.service.setHandlerResolver(new SoapHandlerResolver());
			
			Port port = findPreferredPort(serviceEntry.getValue());		
			
			QName portQName = new QName(port.getBinding().getQName().getNamespaceURI(), port.getName());
			dispatch = this.service.createDispatch(portQName, Source.class, Service.Mode.PAYLOAD);
			//TODO: set this value back to 120000 later; lowering it to a palatable number for now for dev
			dispatch.getRequestContext().put("com.sun.xml.ws.connect.timeout", 2000);
			dispatch.getRequestContext().put("com.sun.xml.ws.request.timeout", 120000);
			
			//TODO Figure out how to send this cookie along!
//			List<Cookie> cookies = CookieUtil.getCookieList();
//			if(cookies!=null && cookies.size()>0){
//				Map<String,Object> reqHttpHeader = 
//					(Map<String,Object>) dispatch.getRequestContext().get(HTTPConstants.REQUEST_HEADERS);
//				if (null==reqHttpHeader) {
//					reqHttpHeader = new Hashtable<String,Object>();
//				}
//				reqHttpHeader.put("Cookie", cookies);
//				dispatch.getRequestContext().put(HTTPConstants.REQUEST_HEADERS, reqHttpHeader);
//			}

//			String cookieString = CookieUtil.generateCookieString();
//			if(cookieString!= null && cookieString!=""){
//				dispatch.getRequestContext().put("Cookie", cookieString);
//			}
		} catch (Exception e) {
			throw new WsdlLoadException(wsdlUrl, e);
		}
		
	}
	
	
	private Port findPreferredPort(javax.wsdl.Service service) {
		Map<QName, Port> ports = service.getPorts();
		
		Iterator<Port> iter = ports.values().iterator();			
		Port thePort = null;
		boolean stop = false;
		while(iter.hasNext() && !stop) {
			Port port = iter.next();			
			if (port.getExtensibilityElements().isEmpty()) {
				SOAPAddress address = (SOAPAddress)port.getExtensibilityElements().iterator().next();
				this.url = address.getLocationURI();
			}			
			Binding binding = port.getBinding();	
			
			List<ExtensibilityElement> extensibilityElements = binding.getExtensibilityElements();
			for (ExtensibilityElement element : extensibilityElements) {
				if (PREFERRED_SOAP_VERSION.isAssignableFrom(element.getClass())) {
					return port; 
				} if (element instanceof SOAPBinding || element instanceof SOAP12Binding || element instanceof SOAPBindingImpl){
					thePort = port;
				}
			}
		}
		return thePort;
	}
	
	public InputStream sendRecieve(OMElement body) throws AxisFault {
				
		Source requestSource = new StreamSource(new StringReader(body.toString()));
		
		Source respOrFaultDetail = null;
		try {
			respOrFaultDetail = (Source) dispatch.invoke(requestSource);
		} catch (SOAPFaultException sfe) {
			respOrFaultDetail = null;
			SOAPFault fault = sfe.getFault();
			Detail detail = fault.getDetail();
			Iterator it = detail.getDetailEntries();
			while (it.hasNext()) {
				DetailEntry detailEntry = (DetailEntry) it.next();				
				respOrFaultDetail = new DOMSource(detailEntry);
			}
		}		
		
		return new ByteArrayInputStream(this.sourceToXMLString(respOrFaultDetail).getBytes());
	}


	private String sourceToXMLString(Source result) {

        String xmlResult = null;
        try {
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer();
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            OutputStream out = new ByteArrayOutputStream();
            StreamResult streamResult = new StreamResult();
            streamResult.setOutputStream(out);
            transformer.transform(result, streamResult);
            xmlResult = streamResult.getOutputStream().toString();
        } catch (TransformerException e) {
            e.printStackTrace();
        }
        return xmlResult;
    }

	/**
	 * Returns the url from the endpoint in the wsdl if it was able to be read.
	 * 
	 */
	@Override
	public String getURL() {
		return url;
	}

	@Override
	public InputStream sendRecieve(Map<String, ?> parameters) throws AxisFault {
		try {
			OMElement payload = this.createSoapBody(parameters);
			//return this.sendRecieve(updateNamespaces(payload));
			return this.sendRecieve(payload);
		} catch (XMLStreamException e) {
			// TODO Auto-generated catch block
			throw new AxisFault(e.getMessage(),e);
		}
	}
	
	protected OMElement createSoapBody (Map<String, ?> parameters) throws XMLStreamException{
		if (axMessage != null && parameters != null && parameters.size() > 0) {
			return axMessage.setParameters(parameters);
		}
		return OMUtils.mapToOMElement(this.operationName, parameters);
	}
	
	@Override
	public void sendRobust(OMElement body) throws AxisFault {
		Source requestSource = new StreamSource(new StringReader(body.toString()));
		try {
			dispatch.invokeOneWay (requestSource);
		} catch (SOAPFaultException sfe) {
			log.warning("Robust message resulted in an error" + sfe.getMessage());			
			log.log(Level.FINE, sfe.getMessage(),sfe);				
		}
	}

	@Override
	public void sendRobust(Map<String, ?> parameters) throws AxisFault {
		OMElement payload = OMUtils.mapToOMElement(this.operationName, parameters);
		this.sendRobust(payload);
	}
}
